﻿

namespace Cid.Models
{
	using System;
	using System.Collections.Generic;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Data.Linq;
	using System.Data.Linq.Mapping;
	using System.Linq;
	using System.Linq.Expressions;
	using System.Text;
	using System.Reflection;

	using Cid.Models.Validation;

	public class Base<T> : IUnique, IBase, IValidatorErrorInfo, IDisposable, ISaveable, IDeletable where T : Base<T>, INotifyPropertyChanged, INotifyPropertyChanging 
	{

		private readonly static List<IValidationRule> s_rules = new List<IValidationRule>();
		private readonly static Dictionary<PropertyInfo, AssociationAttribute> s_attributes = new Dictionary<PropertyInfo, AssociationAttribute>();
		private readonly static Dictionary<PropertyInfo, ColumnAttribute> s_columns = new Dictionary<PropertyInfo, ColumnAttribute>();
		private static TableAttribute table;
		protected static bool s_initialized = false;
		private List<IValidationRule> brokenRules;

		private bool isNew = false;
		private bool isModified = false;
		private bool isDeleted = false;
		private DataContext context = null;


		public virtual object UniqueId
		{
			get { throw new NotImplementedException(); }
		}


		public virtual void Detach() 
		{
			foreach (PropertyInfo property in s_attributes.Keys)
			{
				
				//Type propertyType = property.PropertyType;
				//ConstructorInfo propertyConstructor = propertyType.GetConstructor(new Type[] { });
				//object defaultProperty = propertyConstructor.Invoke(new object[] { });
				//property.SetValue(this, defaultProperty, null);
			}
		}

		protected List<IValidationRule> Rules
		{
			get { return s_rules; }
		}

		public virtual bool IsDeleted
		{
			get { return this.isDeleted; }
		}

		protected void MarkDeleted()
		{
			this.isDeleted = true;
		}
		public bool IsNew
		{
			get { return this.isNew; }
		}

		public virtual bool IsModified 
		{
			get { return this.isModified; }
		}

		public Base()
		{
			this.Initialize();
		}

		public static T New()
		{
			T item = Activator.CreateInstance<T>();
			item.isNew = true;
			item.Validate();
			return item;
		}

		public static T New(IDictionary<string, object> newValues)
		{
			return New().Map(newValues);
		}


		public static T New(object newValues)
		{
			return New().Map(newValues);
		}

		public static T Create()
		{
			return New().Save();
		}

		public static T Create(object newValues)
		{
			return New(newValues).Save();
		}

		public static T Create(IDictionary<string, object> newValues)
		{
			return New(newValues).Save();
		}


		public static T FindOrCreateNew(object id)
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				T item = Find(context, t => t.UniqueId == id, null).FirstOrDefault();
				context.AttachModel(item);

				if (item == null)
					item = New(new Dictionary<string, object> { { "Id", id } });

				return item;
			}
		}

		protected static DataContext CreateContext()
		{
			return ContextProxy.CreateContext();
		}

		protected DataContext GetContext()
		{
			if (this.context == null)
				this.context = CreateContext();
			return this.context;
		}

		public T Save()
		{
			DataContext context = GetContext();
			context.AttachModel((T)this);
			return Save(context);
		}


		public T Save(DataContext context)
		{
			if (!this.IsValid)
			{
				throw new Exception(string.Format("{0} is invalid and can not be saved", this.GetType().Name.Titlize()));
			}

			this.OnSave();
			context.SubmitChanges();
			this.MarkOld();
			return (T)this;
		}

		protected void AddRule(IValidationRule rule)
		{
			this.Rules.Add(rule);
		}

		protected void AddRule(IEnumerable<string> properties, IValidationRule rule) 
		{
			ValidationRule clone = (ValidationRule)rule;

			properties.Each(delegate(string item)
			{
				ValidationRule copy = ((ValidationRule)Activator.CreateInstance(clone.GetType()));
				clone.Each(pair => copy[pair.Key] = pair.Value);
			    copy.Name = item;
				this.AddRule(copy);
			});
		}

		

		protected virtual void OnSave()
		{

		}

		protected T Map(IDictionary<string, object> newValues)
		{
			Type type = this.GetType();
			foreach (KeyValuePair<string, object> item in newValues)
			{
				PropertyInfo property = type.GetProperty(item.Key);
				if (property != null)
					property.SetValue(this, item.Value, null);
			}
			return (T)this;
		}

		protected T Map(object newValues)
		{
			Type type = this.GetType();
			foreach (PropertyInfo item in newValues.GetType().GetProperties())
			{
				PropertyInfo property = type.GetProperty(item.Name);
				if (property != null)
					property.SetValue(this, property.GetValue(newValues, null), null);
			}
			return (T)this;
		}

		public static IEnumerable<T> Find()
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				return Find(context).ToList();
			}
		}

		public static IEnumerable<T> Find(DataContext context, string query,  params object[] parameters)
		{
			parameters.Each(param => param = "'{0}'".Fuse(param.ToString().Replace("'", "''")));
			return context.ExecuteQuery<T>(query, parameters);
		}

		public static IEnumerable<T> Find(string query, params object[] parameters)
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				return Find(context, query, parameters).ToList();
			}
		}


		public static T FindById(object id)
		{

			return Find("SELECT * FROM {0} WHERE [{1}] ".Fuse(table.Name,
				s_columns.First(o => o.Value.IsPrimaryKey).Key.Name) + " = {0} ", id).FirstOrDefault(); 
		}

		public T GetOriginalById(object id) 
		{
			return FindById(id);
		}

		public static IEnumerable<T> Find(DataContext context, Expression<Func<T, bool>> conditions, Expression<Func<T, bool>> includes)
		{

			if (includes != null)
				context.LoadOptions.LoadWith(includes);

			IQueryable<T> query = context.GetTable<T>().Where(conditions);

			return query;
		}

		public static IEnumerable<T> Find(DataContext context)
		{
			IEnumerable<T> query = context.GetTable<T>();
			return query;
		}

		public static IEnumerable<T> Find(Expression<Func<T, bool>> conditions, Expression<Func<T, bool>> includes)
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				return Find(context, conditions, includes).ToList();
			}
		}

		public static IEnumerable<T> Find(Action<IQueryable<T>> action, Expression<Func<T, bool>> includes)
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				context.LoadOptions.LoadWith(includes);
				IQueryable<T> query = context.GetTable<T>();
				action(query);
				return query;
			}
		}

		public static IEnumerable<T> Find(Expression<Func<T, bool>> conditions)
		{
			using (DataContext context = CreateContext())
			{
				context.ObjectTrackingEnabled = false;
				return Find(context, conditions, null).ToList();
			}
		}

		protected List<IValidationRule> BrokenRules
		{
			get
			{
				if (this.brokenRules == null)
					this.brokenRules = new List<IValidationRule>();
				return this.brokenRules;
			}
		}

		public bool IsValid
		{
			get { return this.BrokenRules.Count == 0; }
		}

		protected void MarkModified()
		{
			this.isModified = true;
		}

		protected void MarkOld()
		{
			this.isModified = false;
			this.isNew = false;
		}

		protected virtual void Initialize()
		{
			T item = (T)this;
			item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);

			this.StaticInitialize();	
		}

		protected void Validate()
		{
			foreach (PropertyInfo property in s_columns.Keys)
				this.ValidateProperty(property.Name);
		}

		void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			this.MarkModified();
		}

		protected void StaticInitialize()
		{
			if (!s_initialized)
			{
				s_initialized = true;
				this.GetAttributes();
				this.StaticAddRules();


			}
		}

		protected virtual void StaticAddRules()
		{

		}

		protected virtual void GetAttributes()
		{
			Type type = this.GetType();

			object[] items =	type.GetCustomAttributes(typeof(TableAttribute), true);
			table = items[0] as TableAttribute;


			PropertyInfo[] properties = type.GetProperties();
			foreach (PropertyInfo property in properties)
				this.GetPropertyAttributes(property);
		}

		protected virtual void GetPropertyAttributes(PropertyInfo property)
		{
			IEnumerable<object> items = property.GetCustomAttributes(
											typeof(ValidationAttribute), true);

			foreach (ValidationAttribute attribute in items.Cast<ValidationAttribute>())
				s_rules.Add(attribute.Rule);

			items = property.GetCustomAttributes(typeof(AssociationAttribute), true);
			
			foreach (AssociationAttribute attr in items)
				s_attributes.Add(property, attr);
			
			

			items = property.GetCustomAttributes(typeof(ColumnAttribute), true);
			foreach (ColumnAttribute attr in items)
				s_columns.Add(property, attr);
			
		}

		protected void ValidateProperty(string propertyName)
		{
			var rules = s_rules.Where(
				rule => rule.Name.ToLower() == propertyName.ToLower());

			if (rules.Count() > 0)
			{
				this.ValidateProperty(propertyName,
					this.GetType().GetProperty(propertyName).GetValue(this, null), rules);

			}
		}

		protected void ValidateProperty(string propertyName, object value)
		{
			IEnumerable<IValidationRule> rules = s_rules.Where(rule => rule.Name.ToLower() == propertyName.ToLower());
			if (rules.Count() > 0)
				this.ValidateProperty(propertyName, value, rules);
		}

		protected void ValidateProperty(string propertyName, object value, IEnumerable<IValidationRule> rules)
		{
			foreach (IValidationRule rule in rules)
			{
				if (!rule.Validate(value))
					this.BrokenRules.Add(rule);
				else
					this.BrokenRules.Remove(rule);
			}
		}

		public bool IsSaveable
		{
			get { return (this.IsValid && (this.IsModified || this.IsNew)); }
		}

		#region ISaveable Members

		object ISaveable.Save()
		{
			return this.Save();
		}

		#endregion

		#region IDeletable Members

		public virtual void Delete()
		{
			this.MarkDeleted();
			this.Save();
		}

		void IDeletable.Delete()
		{
			this.Delete();
		}

		#endregion

		#region IValidatorErrorInfo Members

		IEnumerable<IValidationRule> IValidatorErrorInfo.this[string propertyName]
		{
			get { return this.Rules.Where(o => o.Name.ToLower() == propertyName.ToLower()); }
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if(this.context != null) {
				this.context.Dispose();
				this.context = null;
			}
		}

		#endregion
	}
}
